package gugga.collections.linkedList
{
	import gugga.collections.IIterator;
	import gugga.debug.Assertion;
	
	public class LinkedList
	{
		private var mFirstItem : LinkedListItem;
		private var mLastItem : LinkedListItem;
		
		public function LinkedList()
		{
			mFirstItem = null;
			mLastItem = null;
		}
			
		public function get Count() : Number
		{
			var iterator : LinkedListIterator = LinkedListIterator(getIterator());
			var i : Number = 0;
	
			while(iterator.iterate())
			{
				i++;
			}
			
			return i;
		}
		
		public function contains(aItemData:Object) : Boolean
		{
			var iterator : LinkedListIterator = LinkedListIterator(getIterator());
			
			while(iterator.iterate())
			{
				if(aItemData === iterator.CurrentListItem.Data)
				{
					return true;
				}
			}
			
			return false;
		} 
		
		private function containsItem(aItem:LinkedListItem) : Boolean
		{
			if(!aItem)
			{
				Assertion.warning("LinkedList can't contain null item", this, arguments);
				
				return false;
			}
			else
			{
				var currentItem:LinkedListItem = mFirstItem;
			
				while(currentItem != null)
				{
					if(currentItem == aItem)
					{
						return true;
					}
					
					currentItem = currentItem.NextItem;
				}
				return false;				
			}
		}
		
		public function getItemPredecessor(aItem:LinkedListItem) : LinkedListItem
		{
			if(aItem != mFirstItem && containsItem(aItem))
			{
				var iterator:LinkedListIterator = new LinkedListIterator(mFirstItem);
				
				while(iterator.iterate())
				{	
					if(iterator.CurrentListItem.NextItem == aItem)
					{
						return iterator.CurrentListItem;
					}
				}
			}
			return null;
		}
		
		public function insertBefore(aTargetItem:LinkedListItem, aData:Object) : void
		{
			if(containsItem(aTargetItem))
			{
				var newItem : LinkedListItem = new LinkedListItem();
				newItem.Data = aData;
				newItem.NextItem = aTargetItem; 
				
				if(aTargetItem == mFirstItem)
				{
					mFirstItem = newItem;
				}
				else
				{			
					var itemPredecessor : LinkedListItem = getItemPredecessor(aTargetItem);
					itemPredecessor.NextItem = newItem;
				}
			}
			else
			{
				Assertion.warning("Can't insert before Item that isn't in the list", this, arguments);
			}
		}  
		
		public function insertAfter(aTargetItem:LinkedListItem, aData:Object) : void
		{
			if(containsItem(aTargetItem))
			{
				var newItem : LinkedListItem = new LinkedListItem();
				newItem.Data = aData;
				newItem.NextItem = aTargetItem.NextItem; 
				
				aTargetItem.NextItem = newItem;
				
				if(aTargetItem == mLastItem)
				{
					mLastItem = newItem;
				}
			}
			else
			{
				Assertion.warning("Can't insert after Item that isn't in the list", this, arguments);
			}
		}  
	
		public function insertHead(aData:Object) : void
		{
			var newItem : LinkedListItem = new LinkedListItem();
			newItem.Data = aData;
			newItem.NextItem = mFirstItem; 
			
			mFirstItem = newItem;
			
			if(!mLastItem)
			{
				mLastItem = mFirstItem;
			}
		}  
	
		public function insertTail(aData:Object) : void
		{
			var newItem : LinkedListItem = new LinkedListItem();
			newItem.Data = aData;
			newItem.NextItem = null; 
			
			if(mLastItem != null)
			{
				mLastItem.NextItem = newItem;
			}
			mLastItem = newItem;
			
			if(!mFirstItem)
			{
				mFirstItem = mLastItem;
			}
		}  	
	
		public function deleteBefore(aTargetItem:LinkedListItem) : void
		{
			if(aTargetItem != mFirstItem)
			{
				var itemPredecessor : LinkedListItem = getItemPredecessor(aTargetItem);
				
				if(itemPredecessor != null)
				{
					if(itemPredecessor == mFirstItem)
					{
						mFirstItem = aTargetItem;
					}
					else
					{
						var itemPrePredecessor : LinkedListItem = getItemPredecessor(itemPredecessor);				
						itemPrePredecessor.NextItem = aTargetItem;
					}
				}
			}
		}  
	
		public function deleteAfter(aTargetItem:LinkedListItem) : void
		{
			if(containsItem(aTargetItem) && 
    			aTargetItem != mLastItem)
			{
				var nextItem : LinkedListItem = aTargetItem.NextItem;
				aTargetItem.NextItem = nextItem.NextItem;
				
				if(nextItem == mLastItem)
				{
					mLastItem = aTargetItem;
				}
			}
		}  
		
		public function deleteItem(aItem:LinkedListItem) : void
		{
			var itemPredecessor : LinkedListItem = getItemPredecessor(aItem);
			
			if(itemPredecessor)
			{
				itemPredecessor.NextItem = aItem.NextItem;
			}
			
			if(aItem == mFirstItem)
			{
				mFirstItem = aItem.NextItem;
			}
	
			if(aItem == mLastItem)
			{
				mLastItem = itemPredecessor;
			}
		}  
	
		public function deleteHead() : void
		{
			if(mFirstItem != null)
			{
				var currentHead:LinkedListItem = mFirstItem;
				
				mFirstItem = currentHead.NextItem;
				
				currentHead.NextItem = null;
			}
		}  
	
		public function deleteTail() : void
		{
			if(mFirstItem != mLastItem)
			{
				var lastItemPredecessor:LinkedListItem = getItemPredecessor(mLastItem);
			
				lastItemPredecessor.NextItem = null;
				mLastItem = lastItemPredecessor;
			}
			else
			{
				mFirstItem = null;
				mLastItem = null;
			}
		}  
		
		public function getIterator() : IIterator
		{
			var iterator : IIterator = new LinkedListIterator(this.mFirstItem);
			return iterator;
		}	
		
		public function getHead() : Object
		{
			if(mFirstItem != null)
			{
				return mFirstItem.Data;
			}
			else
			{
				return null;
			}
		}
		
		public function getTail() : Object
		{
			if(mLastItem != null)
			{
				return mLastItem.Data;
			}
			else
			{
				return null;
			}
		}
		
		public function isHead(aData:Object) : Boolean
		{
			return (aData === getHead());
		}
		
		public function isTail(aData:Object) : Boolean
		{
			return (aData === getTail());
		}
		
		public function getFirstItemContaining(aData:Object) : LinkedListItem
		{
			return getFirstItemContainingAfter(aData, mFirstItem);
		}
		
		public function getFirstItemContainingAfter(aData:Object, aBeginFrom : LinkedListItem) : LinkedListItem
		{
			Assertion.warningIfNull(aBeginFrom, "aBeginFrom should not be null", this, []);
			
			var iterator : LinkedListIterator = new LinkedListIterator(aBeginFrom);
						
			while(iterator.iterate())
			{
				if(iterator.Current === aData)
				{
					return iterator.CurrentListItem;
				}
			}
			
			return null;
		}
	}
}